use crate::{
    vfs::Inode,
    fs::FileSystem,
    block_dev::BlockDevice,
};

use std::{
    cell::RefCell, 
    io::Write, 
    rc::Rc, str::FromStr,
};

#[allow(unused)]
pub struct Simulator {
    root_inode: Inode,
    current_inode: Inode,
    fs: Rc<RefCell<FileSystem>>,
    exit: bool,
}

impl Simulator {
    pub fn new(device: Box<dyn BlockDevice>) -> Self {
        let fs = FileSystem::open(device);
        Self {
            root_inode: FileSystem::root_inode(&fs),
            current_inode: FileSystem::root_inode(&fs),
            fs,
            exit: false,
        }
    }

    pub fn run(&mut self) -> std::io::Result<()> {
        while !self.exit {
            let mut input = String::new();
            print!("fs> ");
            std::io::stdout().flush()?;
            std::io::stdin().read_line(&mut input)?;
            self.execute(&input)?;
        }
        Ok(())
    }
}

impl Simulator {
    fn execute(&mut self, input: &str) -> std::io::Result<()> {
        let (command, args) = Simulator::parse_input(input);
        match command {
            "clear" => self.clear(args),
            "exit" =>  self.exit = true,
            "ls" =>    self.ls(args),
            "mkdir" => self.mkdir(args),
            "touch" => self.touch(args),
            "cd" =>    self.cd(args),
            "pwd" =>   self.pwd(args),
            _ => {
                print!("command not found: {}\n", command);
            },
        };
        std::io::stdout().flush()
    }
}

impl Simulator {
    fn clear(&self, _args: Vec<&str>) {
        print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
        std::io::stdout().flush().expect("Flush stdout failed!");
    }

    fn ls(&self, args: Vec<&str>) {
        // Gei option for ls
        let ls_all = args.iter().any(|arg| *arg == "-a");
        let _ls_l = args.iter().any(|arg| *arg == "-l");

        let do_path_ls = |path: &str| {
            match self.find_path(path) {
                None => println!("ls: cannot access '{}': No such file or directory", path),
                Some(inode) => {
                    let names = inode.ls().unwrap();
                    let names = if ls_all {
                        names
                    } else {
                        names.into_iter().filter(|name| !name.starts_with('.')).collect()
                    };
                    for name in names {
                        print!("{} ", name);
                    }
                    println!("")
                }
            }
        };
        
        // Get paths
        let mut paths: Vec<&str> = args.into_iter().filter(|arg| !arg.starts_with('-')).collect();
        if paths.is_empty() {
            paths.push(".");
        }
        let paths = paths;

        if paths.len() == 1 {
            do_path_ls(paths[0]);
        } else {
            for (index, path) in paths.into_iter().enumerate() {
                if index != 0 {
                    println!();
                }
                println!("{}:", path);
                do_path_ls(path);
            }
        }
    }

    fn mkdir(&self, args: Vec<&str>) {
        if args.len() == 0 {
            print!("mkdir: needs args as directory names\n");
        } else {
            for name in args {
                self.current_inode.create_directory(name).unwrap();
            }
        }
    }

    fn touch(&self, args: Vec<&str>) {
        if args.len() == 0 {
            println!("touch: needs args as file names");
        } else {
            for name in args {
                self.current_inode.create_file(name).unwrap();
            }
        }
    }

    fn cd(&mut self, args: Vec<&str>) {
        if args.len() != 1 {
            println!("cd: needs one arg as directory names");
        } else {
            match self.find_path(args[0]) {
                None => {
                    println!("cd: no such file or directory: {}", args[0]);
                }
                Some(inode) => {
                    if !inode.is_directory() {
                        println!("cd: not a directory: {}", args[0]);
                    } else {
                        self.current_inode = inode;
                    }
                }
            }
        }
    } 

    fn pwd(&self, _args: Vec<&str>) {
        let mut names: Vec<String> = Vec::new();
        let mut inode = self.current_inode.clone();
        
        loop {
            match inode.parent_inode() {
                None => break,
                Some(parent_inode) => {
                    names.push(inode.name());
                    inode = parent_inode;
                }
            }
        }

        // e.g: names: ["d2", "d1", "d0"]
        let dir_size = names.len();
        let mut path: String = String::from_str("/").unwrap();
        for (index, name) in names.into_iter().rev().enumerate() {
            path.push_str(&name);
            if index + 1 != dir_size {
                path.push('/');
            }
        }

        println!("{}", path);
    }

    #[allow(unused)]
    fn rm(&self, _args: Vec<&str>) {
        todo!();
    }
}

impl Simulator {
    fn find_path(&self, path: &str) -> Option<Inode> {
        let mut inode: Inode;
        let relative_path;
        if path.starts_with('/') {
            inode = self.root_inode.clone();
            relative_path = &path[1..];
        } else {
            inode = self.current_inode.clone();
            relative_path = &path[..];
        };

        let names: Vec<&str> = relative_path.split('/').filter(|s| !s.is_empty()).collect();
        
        for name in names {
            match inode.find(name) {
                None => return None,
                Some(next_inode) => inode = next_inode,
            }
        }

        Some(inode)
    }
}

impl Simulator {
    fn parse_input(input: &str) -> (&str, Vec<&str>) {
        let input = input.trim();
        let mut token_iter = input.split(' ');
        let command = token_iter.next().unwrap().trim();
        (command, token_iter.into_iter()
            .filter(|token| !token.is_empty())
            .map(|token| token.trim()).collect())
    }
}